import React, { useRef, useEffect } from 'react';

export const useClickOutside = (ref: React.RefObject<any>, callback: (e: Event) => void) => {
  const handleClick = (e: Event) => {
    if (ref.current && !ref.current.contains(e.target)) {
      callback(e);
    }
  };
  React.useEffect(() => {
    document.addEventListener('click', handleClick);
    return () => {
      document.removeEventListener('click', handleClick);
    };
  });
};

export const useClickPage = (callback: (e: Event) => void) => {
  const handleClick = (e: Event) => {
    callback(e);
  };
  React.useEffect(() => {
    document.addEventListener('click', handleClick);
    return () => {
      document.removeEventListener('click', handleClick);
    };
  });
};

export const useClickInside = (ref: React.RefObject<any>, callback: (e: Event) => void, stopPropagation = false) => {
  const handleClick = (e: Event) => {
    if (stopPropagation) {
      e.stopPropagation();
    }
    if (ref.current && ref.current.contains(e.target)) {
      callback(e);
    }
  };
  React.useEffect(() => {
    document.addEventListener('click', handleClick);
    return () => {
      document.removeEventListener('click', handleClick);
    };
  });
};

export const useComponentDidMount = (onMountHandler: () => void) => {
  React.useEffect(() => {
    onMountHandler();
  }, []);
};

export const useComponentWillUnmount = (onUnmountHandler: () => void) => {
  React.useEffect(
    () => () => {
      onUnmountHandler();
    },
    [],
  );
};

export const usePrevious = <T>(value: T) => {
  const ref = React.useRef<T>();
  React.useEffect(() => {
    ref.current = value;
  });
  return ref.current;
};

export const useTimeout = (callback: () => void, delay: number, deps: any[] = []) => {
  const savedCallback = React.useRef<() => void>();

  React.useEffect(() => {
    savedCallback.current = callback;
  }, [callback]);

  React.useEffect(() => {
    function tick() {
      savedCallback?.current?.();
    }
    if (delay !== null) {
      const id = setTimeout(tick, delay);
      return () => clearTimeout(id);
    }
  }, [delay, ...deps]);
};

export const useInterval = (callback: () => void, delay: number | null, deps: any[] = []) => {
  const savedCallback = React.useRef<() => void>();

  React.useEffect(() => {
    savedCallback.current = callback;
  }, [callback]);

  React.useEffect(() => {
    function tick() {
      savedCallback?.current?.();
    }
    if (delay !== null) {
      const id = setInterval(tick, delay);
      return () => clearInterval(id);
    }
  }, [delay, ...deps]);
};

export function useThrottledCallback<A extends any[]>(callback: (...args: A) => void, wait: number) {
  // track args & timeout handle between calls
  const disableRef = useRef<boolean>();
  const timeout = useRef<ReturnType<typeof setTimeout>>();

  function cleanup() {
    if (timeout.current) {
      clearTimeout(timeout.current);
    }
  }

  // make sure our timeout gets cleared if
  useEffect(() => {
    return () => {
      cleanup();
      timeout.current = undefined;
    };
  }, []);

  return function throttleCallback(...args: A) {
    // capture latest args

    if (disableRef.current) {
      return;
    }

    disableRef.current = true;
    // clear debounce timer

    callback(...args);

    cleanup();
    timeout.current = setTimeout(() => {
      disableRef.current = false;
    }, wait);
  };
}

export function useDebouncedCallback<A extends any[]>(callback: (...args: A) => void, wait: number) {
  // track args & timeout handle between calls
  const argsRef = useRef<A>();
  const timeout = useRef<ReturnType<typeof setTimeout>>();

  function cleanup() {
    if (timeout.current) {
      clearTimeout(timeout.current);
    }
  }

  // make sure our timeout gets cleared if
  // our consuming component gets unmounted
  useEffect(() => {
    return () => {
      cleanup();
      timeout.current = undefined;
    };
  }, []);

  return function debouncedCallback(...args: A) {
    // capture latest args
    argsRef.current = args;

    console.log();

    // clear debounce timer
    cleanup();

    // start waiting again
    timeout.current = setTimeout(() => {
      if (argsRef.current) {
        callback(...argsRef.current);
      }
    }, wait);
  };
}
